package jastgen.generator;

import static jastgen.generator.Helper.*;
import java.io.PrintWriter;
import java.util.Set;
import java.util.TreeSet;

import jastgen.CompileException;
import jastgen.nodes.ClassDef;
import jastgen.nodes.Field;
import jastgen.nodes.Module;
import jastgen.nodes.Type;

import static jastgen.generator.Generator.*;

class TreePrinterBodyGenerator implements BodyGenerator {

	private final Set<String> nodeTypes;
	private final Module node;

	TreePrinterBodyGenerator(
			final Set<String> nodeTypes,
			final Module node
		) throws CompileException {

		this.nodeTypes = nodeTypes;
		this.node = node;
	}

	public void generate(
			final PrintWriter out)
	{
		generateVisitorBody(out, node);
	}

	private void generateVisitorBody(
			final PrintWriter out,
			final Module node)
	{
		// TODO: 2つともbooleanのフラグで良いが、参照渡しできないので
		final Set<String> nodesToVisit = new TreeSet<String>();
		final Set<String> usedContainers = new TreeSet<String>();

		out.println("public final class TreePrinter implements VoidVisitor {");
		out.println();
		out.println("	private final PrintWriter out;");
		out.println("	private int depth;");
		out.println();
		out.println("	public TreePrinter(");
		out.println("			final PrintWriter out)");
		out.println("	{");
		out.println("		this.out = out;");
		out.println("	}");

		for(final ClassDef c : node.getClassDefs()) {
			out.println();
			generateVisitorBodyClass1(
					out, c,
					nodesToVisit,
					usedContainers);
		}

		// TODO: リストではなくフラグでOK
		if(usedContainers.size() != 0) {
			out.println();
			printPrintCollection(out);
		}

		// TODO: リストではなくフラグでOK
		// TODO: 無条件に出力して構わないのでは? (常にtrueでは?)
		if(nodesToVisit.size() != 0) {
			out.println();
			printPrint(out);
		}

		out.println();
		out.println("	private void printLabel(");
		out.println("			final String label)");
		out.println("	{");
		out.println("		printTabs(out, depth);");
		out.println("		out.print(label + \" = \");");
		out.println("	}");
		out.println();
		out.println("	private static void printTabs(");
		out.println("			final PrintWriter out,");
		out.println("			final int depth)");
		out.println("	{");
		out.println("		for(int i=0; i<depth; ++i)");
		out.println("			out.print(\"\t\");");
		out.println("	}");
		out.println();
		out.println("	/**");
		out.println("	 * このクラスを使用して、引数の文字列表現を取得する。");
		out.println("	 * ノードクラスのtoStringの実装に使用する。");
		out.println("	 */");
		out.println("	public static String toString(");
		out.println("			final Node n)");
		out.println("	{");
		out.println("		final StringWriter sw = new StringWriter();");
		out.println("		final PrintWriter pw = new PrintWriter(new BufferedWriter(sw));");
		out.println("		n.visit(new TreePrinter(pw));");
		out.println("		pw.flush();");
		out.println("		return sw.toString();");
		out.println("	}");
		out.println("}");
	}

	private void printPrint(
			final PrintWriter out)
	{
		out.println("	private <T extends Node> void print(");
		out.println("			final T x)");
		out.println("	{");
		out.println("		if(x == null)");
		out.println("			out.print(x);  // null");
		out.println("		else");
		out.println("			x.visit(this);");
		out.println("	}");
	}

	private void printPrintCollection(
			final PrintWriter out)
	{
		out.println("	private <T extends Node> void print(");
		out.println("			final Collection<T> xs)");
		out.println("	{");
		out.println("		if(xs == null)");
		out.println("			out.print(\"null\");");
		out.println("		else {");
		out.println("			out.print(\"[\");");
		out.println("			++depth;");
		out.println("			boolean is1st = true;");
		out.println("			for(final T x : xs) {");
		out.println("				if(!is1st) out.print(\",\");");
		out.println("				is1st = false;");
		out.println("				out.println();");
		out.println("				printTabs(out, depth);");
		out.println("				print(x);");
		out.println("			}");
		out.println("			--depth;");
		out.println("			out.println();");
		out.println("			printTabs(out, depth);");
		out.println("			out.print(\"]\");");
		out.println("		}");
		out.println("	}");
	}

	private void generateVisitorBodyClass1(
			final PrintWriter out,
			final ClassDef node,
			final Set<String> nodesToVisit,
			final Set<String> usedContainers)
	{
		final String n = node.getBaseName();

		printVisitC(out, node, "in");
		out.println("	{");
		out.println("		++depth;");
		out.println();

		if(node.getFields().isEmpty())  // ここはhasNormalFieldsではなくてよい
			out.println("		out.print(\"" + n + "()\");");
		else {
			out.println("		out.println(\"" + n + "(\");");
			out.println();

			boolean is1st = true;
			for(final Field f : node.getFields()) {

				if(!is1st) {
					out.println("		out.println(\",\");");
					out.println();
				}
				is1st = false;

				generateVisitorBodyMember(
						out, f,
						nodesToVisit,
						usedContainers);
			}

			out.println("		out.print(\")\");");
		}

		out.println();
		out.println("		--depth;");
		out.println("	}");
	}

	// VoidVisitorImpl、VoidVisitorAdapter、VisitorImpl、NodeReplacer、TreePrinterの5つは大部分が共通
	private void generateVisitorBodyMember(
			final PrintWriter out,
			final Field node,
			final Set<String> nodesToVisit,
			final Set<String> usedContainers)
	{
		out.println("		printLabel(\"" + node.getName() + "\");");

		final String n = node.getNameInMethod();
		final Type type = node.getType();

		if(type.maybeContainerToVisit()) {
			final String inner = type.getOnlyParam().toString();
			if(nodeTypes.contains(inner)) {
				// 型パラメータが1つで、ノードクラスであり、
				// 外側のクラスはcontainerPrefixesに含まれる場合。
				usedContainers.add(inner);
				nodesToVisit.add(inner);
				out.println("		print(node.get" + n + "());");
				return;
			}
		}

		if(nodeTypes.contains(type.toString())) {
			nodesToVisit.add(type.toString());
			out.println("		print(node.get" + n + "());");
			return;
		}

		out.println("		out.print(node." + getOrIs(node) + n + "());");
	}
}
